/*
 * Copyright (c) [2019] [zr]
 *    [bsf] is licensed under the Mulan PSL v1.
 *    You can use this software according to the terms and conditions of the Mulan PSL v1.
 *    You may obtain a copy of Mulan PSL v1 at:
 *       http://license.coscl.org.cn/MulanPSL
 *    THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
 *    IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
 *    PURPOSE.
 *
 *    See the Mulan PSL v1 for more details.
 */

package bsf

import (
	"fmt"
	"gitee.com/zr233/bsf/config"
	"gitee.com/zr233/bsf/storage"
	"github.com/gin-gonic/gin"
	"github.com/sirupsen/logrus"
	"reflect"
	"regexp"
	"strings"
)

type Engine struct {
	*gin.Engine
	configManager *config.Manager
	storage       *storage.Collection
	logger        logrus.FieldLogger
}

func (e *Engine) Logger() logrus.FieldLogger {
	return e.logger
}
func (e *Engine) UseConfigCenter() (err error) {
	e.configManager, err = config.NewManager(e)
	if err != nil {
		return
	}
	return
}

func (e *Engine) UseStorage() (err error) {
	e.storage, err = storage.NewCollection(e.configManager, e)

	return
}

type Router struct {
	gin.IRouter
}

/*
自动注册controller类的所有公有方法
路由地址为 relativePath/controller名/方法名
*/
func (e *Engine) AutoRegisterController(relativePath string, controllerPtr Controller) *Engine {
	autoRegisterControllerToIRoutes(e, relativePath, controllerPtr)
	return e
}

/*
自动注册controller类的所有公有方法
路由地址为 relativePath/controller名/方法名
*/
func (r *Router) AutoRegisterController(relativePath string, controllerPtr Controller) *Router {
	autoRegisterControllerToIRoutes(r, relativePath, controllerPtr)
	return r
}

func (e *Engine) Group(relativePath string, handlers ...gin.HandlerFunc) *Router {
	newEngine := &Router{
		e.Engine.Group(relativePath, handlers...),
	}
	return newEngine
}

func autoRegisterControllerToIRoutes(routers gin.IRoutes, relativePath string, controllerPtr Controller) {
	methodMap := parseController(controllerPtr)

	for methodPath, methodFunc := range methodMap {
		methodPathArr := strings.Split(methodPath, "/")

		httpMethod, methodName := getHttpMethod(methodPathArr[1])

		finalPath := strings.Join([]string{
			relativePath,
			methodPathArr[0],
			methodName,
		}, "/")

		switch httpMethod {
		case HttpMethodAny:
			routers.Any(finalPath, methodFunc)
		case HttpMethodGet:
			routers.GET(finalPath, methodFunc)
		case HttpMethodPost:
			routers.POST(finalPath, methodFunc)
		case HttpMethodDELETE:
			routers.DELETE(finalPath, methodFunc)
		case HttpMethodPATCH:
			routers.PATCH(finalPath, methodFunc)
		case HttpMethodPUT:
			routers.PUT(finalPath, methodFunc)
		case HttpMethodOPTIONS:
			routers.OPTIONS(finalPath, methodFunc)
		case HttpMethodHEAD:
			routers.HEAD(finalPath, methodFunc)
		}
	}
}

func controllerFieldOk(valueOfControllerPtr reflect.Value) bool {
	return getBaseControllerValue(valueOfControllerPtr).IsValid()
}

func getBaseControllerValue(valueOfControllerPtr reflect.Value) reflect.Value {
	return valueOfControllerPtr.Elem().FieldByName("ControllerBase")
}

func ginHandlerFunc(context *gin.Context, controllerPtrType reflect.Type, methodName string) {
	controllerPtrValue := reflect.New(controllerPtrType.Elem())

	fieldPtrValue := controllerPtrValue.Elem().FieldByName("ControllerBase")
	if controllerBase, ok := fieldPtrValue.Interface().(*ControllerBase); !ok {
		panic(fmt.Errorf("[bsf]class[%s]does not have filed[*bsf.ControllerBase]", controllerPtrType.Name()))
	} else {
		if controllerBase == nil {
			fieldPtrValue.Set(reflect.ValueOf(newControllerBase(context)))
		}
		fieldPtrValue.Interface().(*ControllerBase).ctx = context
	}

	methodValue := controllerPtrValue.MethodByName(methodName)

	resultList := methodValue.Call(nil)
	errInterface := resultList[0].Interface()
	if errInterface != nil {
		err := errInterface.(error)
		_ = context.Error(err)
	}
}

func parseController(controllerPtr Controller) (result map[string]gin.HandlerFunc) {
	result = make(map[string]gin.HandlerFunc)

	typeOfControllerPtr := reflect.TypeOf(controllerPtr)
	valueOfControllerPtr := reflect.ValueOf(controllerPtr)
	className := typeOfControllerPtr.String()

	name := typeOfControllerPtr.String()
	namePath := strings.Split(name, ".")
	name = namePath[1]

	num := typeOfControllerPtr.NumMethod()

	for i := 0; i < num; i++ {
		method := typeOfControllerPtr.Method(i)
		methodName := method.Name
		//排除controller基类方法
		if _, ok := controllerBaseExcludeMethods[methodName]; ok {
			continue
		}

		funcType := valueOfControllerPtr.MethodByName(methodName).String()

		const funcTypeString = "<func() error Value>"
		if funcType != funcTypeString {
			panic(fmt.Errorf("[bsf]: class [%s] method [%s] format error, should be %s, but is %s",
				className, methodName, funcTypeString, funcType))
		}

		methodPath := name + "/" + methodName

		result[methodPath] = func(ctx *gin.Context) {
			ginHandlerFunc(ctx, typeOfControllerPtr, methodName)
		}
	}

	return
}

type HttpMethod int

const (
	HttpMethodAny HttpMethod = iota
	HttpMethodGet
	HttpMethodPost
	HttpMethodDELETE
	HttpMethodPATCH
	HttpMethodPUT
	HttpMethodOPTIONS
	HttpMethodHEAD
)

func getHttpMethod(methodName string) (h HttpMethod, NewName string) {
	h = HttpMethodAny
	NewName = methodName
	methodNameBytes := []byte(methodName)
	reg := regexp.MustCompile(`^(HttpGet|HttpPost|HttpDelete|HttpPatch|HttpPut|HttpOptions|HttpHead)`)
	loc := reg.FindIndex(methodNameBytes)
	if loc != nil {
		method := string(methodNameBytes[:loc[1]])
		switch method {
		case "HttpGet":
			h = HttpMethodGet
		case "HttpPost":
			h = HttpMethodPost
		case "HttpDelete":
			h = HttpMethodDELETE
		case "HttpPatch":
			h = HttpMethodPATCH
		case "HttpPut":
			h = HttpMethodPUT
		case "HttpOptions":
			h = HttpMethodOPTIONS
		case "HttpHead":
			h = HttpMethodHEAD
		}
		NewName = string(methodNameBytes[loc[1]:])
	}
	return
}
